package com.rslai.base.tool.servertest.transport.http;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rslai.base.tool.autotest.CaseIdHolder;
import com.rslai.base.tool.servertest.exception.UnsupportedFilterResultType;
import com.rslai.base.tool.servertest.model.KeyValueStore;
import com.rslai.base.tool.servertest.response.Response;
import com.rslai.base.tool.servertest.util.PropertyUtils;
import com.rslai.commons.http.RslaiHttpClient;
import com.rslai.commons.http.data.HttpParameter;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpVersion;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.URI;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class HttpService {
    private static final Logger logger = LoggerFactory.getLogger(HttpService.class);
    public static RslaiHttpClient httpClient;
    static int connTimeout = 10000;
    static int readTimeout = 10000;
    static int maxTotal = 200;
    static int maxPerRoute = 50;
    private static Map headers;
    private static final String CONTENT_TYPE = "Content-Type";

    static {
        httpClient = RslaiHttpClient.createDefaultClient(connTimeout, readTimeout, maxTotal, maxPerRoute);

        httpClient.setRequestGlobalCookies();
        httpClient.allowCookiePolicy();
        HttpParams params = httpClient.getParams();
        params.setParameter("http.socket.timeout", Integer.valueOf(600000));
        params.setParameter("http.connection.stalecheck", Boolean.valueOf(true));
        if ("true".equals(PropertyUtils.getProperty("enableRedirect", "true")))
            params.setParameter("http.protocol.handle-redirects", Boolean.TRUE);
        else {
            params.setParameter("http.protocol.handle-redirects", Boolean.FALSE);
        }
        httpClient.getParams().setParameter("http.protocol.version", httpVersion());
        httpClient.setDebug(httpDebug());
    }

    public static Response get(String url, List<KeyValueStore> params) {
        org.apache.http.HttpResponse httpResponse = doGet(url, params);
        return response(httpResponse);
    }

    public static Response getWithStream(String url, List<KeyValueStore> params) {
        org.apache.http.HttpResponse httpResponse = doGet(url, params);
        return responseWithStream(httpResponse);
    }

    /**
     * dto类型的参数如果需要以 BodyEntity 发送,需要转为 json 字符串形式
     *
     * @param params 参数
     * @return 转为 json 字符串的参数
     */
    public static List<KeyValueStore> dtoToBodyEntity(List<KeyValueStore> params) {
        List<KeyValueStore> list = new LinkedList<KeyValueStore>();
        if ((params != null) && (params.size() == 2) && (containsKey(params, "param"))) { // 如果是 dto 类型的 BodyEntity 则转为普通 BodyEntity
            for (KeyValueStore kvs : params) {
                if (!"param".equals(kvs.getName())) {
                    Object obj = kvs.getValue();
                    if (obj instanceof String) {
                        list.add(new KeyValueStore("param", (String) obj));
                        return list;
                    } else {
                        JSONObject jsonObject = (JSONObject) JSON.toJSON(obj);
                        list.add(new KeyValueStore("param", jsonObject.toJSONString()));
                        return list;
                    }
                }
            }
        }
        return params;
    }

    public static Response entityRequest(String url, String method, List<KeyValueStore> params) {
        HttpRequestBase request = null;
        try {
            params = dtoToBodyEntity(params); // 如果是 dto 类型的 BodyEntity 则转为普通 BodyEntity

            request = doEntityRequest(url, method, params);
            org.apache.http.HttpResponse httpResponse = httpClient.execute(request);
            if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == 302) {
                // 返回 302 后自动跳转
                String redirectURL = httpResponse.getFirstHeader("location").getValue();
                if (!containProtocol(redirectURL)) {
                    redirectURL = getDomain(request.getURI().toString()) + redirectURL; // 如果302的location不包括域名，则从当前中request中得到域名添加上
                }
                return entityRequest(redirectURL, "get", null); // 递归调用 entityRequest
            }
            return response(httpResponse);
        } catch (Exception e) {
            if (request != null) {
                request.abort();
            }
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 判断url中是否包括协议
     * @param url
     * @return true
     */
    private static boolean containProtocol(String url) {
        String regex = "^(https|ftp|http)?://.*$";
        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(url);
        return matcher.matches();
    }

    /**
     * 返回协议及域名
     * @param url url
     * @return 协议及域名
     */
    private static String getDomain(String url) {
        String re = "((http|ftp|https)://)(([a-zA-Z0-9._-]+)|([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}))(([a-zA-Z]{2,6})|(:[0-9]{1,4})?)";
        Pattern pattern = Pattern.compile(re, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(url);
        String domain = "";
        if (matcher.matches()) {
            domain = url;
        } else {
            String[] split2 = url.split(re);
            if (split2.length > 1) {
                String substring = url.substring(0, url.length() - split2[1].length());
                domain = substring;
            } else {
                domain = split2[0];
            }
        }
        return domain;
    }

    public static org.apache.http.HttpResponse getHttpResponse(String url, String method, List<KeyValueStore> params) {
        HttpRequestBase request = null;
        try {
            request = doEntityRequest(url, method, params);
            return httpClient.execute(request);
        } catch (Exception e) {
            if (request != null) {
                request.abort();
            }
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public static Response entityRequestWithStream(String url, String method, List<KeyValueStore> params) {
        HttpRequestBase request = null;
        try {
            request = doEntityRequest(url, method, params);
            org.apache.http.HttpResponse httpResponse = httpClient.execute(request);
            return responseWithStream(httpResponse);
        } catch (Exception e) {
            if (request != null) {
                request.abort();
            }
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    private static Response responseWithStream(org.apache.http.HttpResponse httpResponse) {
        int statusCode = httpResponse.getStatusLine().getStatusCode();
        InputStream content = null;
        try {
            content = httpResponse.getEntity().getContent();
            return new com.rslai.base.tool.servertest.response.HttpResponse(Integer.valueOf(statusCode), content, httpResponse.getAllHeaders());
        } catch (IOException e) {
            IOUtils.closeQuietly(content);
        }
        throw new RuntimeException("读取网络流出错");
    }

    private static HttpVersion httpVersion() {
        String httpVersion = PropertyUtils.getProperty("http.version", "1.1");
        logger.info("Use http version {}", httpVersion);
        if (httpVersion.equals("1.1")) {
            return HttpVersion.HTTP_1_1;
        }
        return HttpVersion.HTTP_1_0;
    }

    private static boolean httpDebug() {
        String httpDebug = PropertyUtils.getProperty("http.debug", "false").toLowerCase();
        logger.info("Use http debug {}", httpDebug);
        if (httpDebug.equals("true")) {
            return true;
        }
        return false;
    }

    private static com.rslai.base.tool.servertest.response.HttpResponse response(org.apache.http.HttpResponse httpResponse) {
        return new com.rslai.base.tool.servertest.response.HttpResponse(Integer.valueOf(httpResponse.getStatusLine().getStatusCode()), getContent(httpResponse), httpResponse.getAllHeaders());
    }

    private static HttpRequestBase doEntityRequest(String url, String method, List<KeyValueStore> params) throws Exception {
        AbstractHttpEntity entity = null;
        if (isBodyEntity(params)) {
            Object entityBody = getEntityBody(params);
            if (isByteArray(entityBody))
                entity = new ByteArrayEntity(getByteArray((Object[]) entityBody));
            else if (isString(entityBody))
                entity = new StringEntity((String) entityBody, "UTF-8");
            else {
                throw new UnsupportedFilterResultType(String.format("filter result type %s is unsupported.", new Object[]{entityBody.getClass().getCanonicalName()}));
            }
            HttpRequestBase request = doEntityRequest(url, method, entity);
            Header[] headers = request.getHeaders("Content-Type");
            if (headers.length == 0) {
                request.setHeader("Content-Type", "application/json");
            }
            return request;
        }
        HttpParameter httpParameter = getParameters(convertRequestParameter(params));
        entity = new UrlEncodedFormEntity(httpParameter.getNameValuePairs(), "UTF-8");
        return doEntityRequest(url, method, entity);
    }

    public static byte[] getByteArray(Object[] entityBody) {
        byte[] result = new byte[entityBody.length];
        for (int i = 0; i < entityBody.length; i++) {
            result[i] = ((Byte) entityBody[i]).byteValue();
        }
        return result;
    }

    public static boolean isString(Object value) {
        return (value != null) && ((value instanceof String));
    }

    public static boolean isByteArray(Object value) {
        if ((value != null) && (value.getClass().isArray())) {
            int length = Array.getLength(value);
            for (int i = 0; i < length; i++) {
                Object o = Array.get(value, i);
                if (!(o instanceof Byte)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    public static Object getEntityBody(List<KeyValueStore> params) {
        if (params != null) {
            for (KeyValueStore kvs : params) {
                if ("param".equals(kvs.getName())) {
                    return kvs.getValue();
                }
            }
        }
        return null;
    }

    public static boolean isBodyEntity(List<KeyValueStore> params) {
        return (params != null) && (params.size() == 1) && (containsKey(params, "param"));
    }

    public static boolean containsKey(List<KeyValueStore> params, String param) {
        for (KeyValueStore kvs : params) {
            if (param.equals(kvs.getName())) {
                return true;
            }
        }
        return false;
    }

    static class HttpEntityRequest extends HttpEntityEnclosingRequestBase {
        final String method;

        public String getMethod() {
            return method;
        }

        HttpEntityRequest(String method) {
            super();
            this.method = method.toUpperCase();
        }
    }

    private static HttpRequestBase doEntityRequest(String url, String method, AbstractHttpEntity entity) {
        HttpEntityEnclosingRequestBase request = new HttpEntityRequest(method);

        String host = DNS.getHost(url);
        url = urlWithIp(url);
        request.setURI(URI.create(url));
        request.setHeader("Host", host);
        setHeaders(request);
        setCaseIdToHeader(request);
        request.setEntity(entity);
        return request;
    }

    private static void setCaseIdToHeader(HttpRequestBase request) {
        String caseId = CaseIdHolder.get();
        String logHostUrl = PropertyUtils.getProperty("autotest-log.host");
        if ((caseId != null) && (StringUtils.isNotBlank(logHostUrl)))
            request.setHeader("autotest_case_id", CaseIdHolder.get());
    }

    public static void setHeaders(HttpRequestBase request) {
        Iterator i$;
        if ((headers != null) && (headers.size() > 0))
            for (i$ = headers.keySet().iterator(); i$.hasNext(); ) {
                Object key = i$.next();
                if ((key != null) && (headers.get(key) != null)) {
                    request.setHeader(key.toString(), headers.get(key).toString());
                }
            }
    }

    public static org.apache.http.HttpResponse doGet(String url, List<KeyValueStore> params) {
        HttpGet httpGet = null;
        try {
            String host = DNS.getHost(url);
            url = urlWithIp(url);
            httpGet = new HttpGet(getHttpGetURL(url, params));
            httpGet.setHeader("Host", host);
            setHeaders(httpGet);
            setCaseIdToHeader(httpGet);
            return httpClient.execute(httpGet);
        } catch (IOException e) {
            if (httpGet != null) {
                httpGet.abort();
            }
            throw new RuntimeException(String.format("访问(%s)出错", new Object[]{url}), e);
        }
    }

    public static String getContent(org.apache.http.HttpResponse httpResponse) {
        try {
            HttpEntity entity = httpResponse.getEntity();
            return EntityUtils.toString(entity, "UTF-8");
        } catch (IOException e) {
            throw new RuntimeException("读取网络流出错", e);
        }
    }

    protected static String getHttpGetURL(String url, List<KeyValueStore> parameters) {
        if ((parameters == null) || (StringUtils.isEmpty(url))) {
            return url;
        }
        StringBuilder sb = new StringBuilder();
        sb.append(url);
        if (!StringUtils.endsWith(url, "?")) {
            sb.append("?");
        }
        for (KeyValueStore kvs : parameters) {
            String name = kvs.getName();
            String value = encode((String) kvs.getValue());
            sb.append(name).append("=").append(value).append("&");
        }
        String result = sb.toString();
        if (result.endsWith("&")) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    public static String urlWithIp(String url) {
        if (StringUtils.isBlank(url)) return url;
        String host = DNS.getHost(url);
        String ip = DNS.dnsLookup(host);
        if (!host.equalsIgnoreCase(ip))
            url = url.replace(host, ip);
        return fixUrlPrefix(url);
    }

    private static String fixUrlPrefix(String url) {
        boolean hasPrefix = StringUtils.startsWithAny(StringUtils.lowerCase(url), new String[]{"http://", "https://"});
        if (!hasPrefix) {
            logger.warn("Your url should with a protocol:{}", url);
            return String.format("http://%s", new Object[]{url});
        }
        return url;
    }

    private static String encode(String value) {
        try {
            if (value != null)
                return URLEncoder.encode(value, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }
        return null;
    }

    private static Map<String, List<String>> convertRequestParameter(List<KeyValueStore> parameters) {
        Map param = new HashMap();
        if ((parameters != null) && (!parameters.isEmpty())) {
            for (KeyValueStore kvs : parameters) {
                List list = (List) param.get(kvs.getName());
                if (list == null) {
                    list = new ArrayList();
                    Object obj = kvs.getValue();
                    if (obj instanceof String) {
                        list.add((String) kvs.getValue());
                    } else if (obj instanceof Map) {
                        list.add((Map) kvs.getValue());
                    }
                    param.put(kvs.getName(), list);
                } else {
                    Object obj = kvs.getValue();
                    if (obj instanceof String) {
                        list.add((String) obj);
                    } else if (obj instanceof Map) {
                        list.add((Map) obj);
                    }
                }
            }
        }
        return param;
    }

    public static HttpParameter getParameters(Map<String, List<String>> parameters) {
        HttpParameter httpParameter = new HttpParameter();
        if ((parameters == null) || (parameters.isEmpty())) {
            return httpParameter;
        }
        Map.Entry entry;
        for (Iterator i$ = parameters.entrySet().iterator(); i$.hasNext(); ) {
            entry = (Map.Entry) i$.next();
            for (Object value : (List) entry.getValue()) {
                if (value instanceof String) {
                    httpParameter.put((String) entry.getKey(), (String) value);
                } else if (value instanceof Map) {
                    for (Object obj : ((Map) value).entrySet()) {
                        Map.Entry valEntry = (Map.Entry) obj;
                        httpParameter.put((String) (valEntry).getKey(), (String) (valEntry).getValue());
                    }
                }
            }
        }
        return httpParameter;
    }

    public static void setHeaders(Map headers) {
        if (headers != null)
            HttpService.headers = headers;
    }

    public static void removeHeader(String header) {
        if (StringUtils.isBlank(header)) return;
        if ((headers == null) || (headers.size() <= 0)) return;
        if ("Cookie".equals(header)) {
            httpClient.setCookieStore(new BasicCookieStore());
        }
        for (Iterator i$ = headers.keySet().iterator(); i$.hasNext(); ) {
            Object key = i$.next();
            if (key != null) {
                String keyStr = key.toString();

                if (keyStr.equals(header)) {
                    headers.remove(header);
                    return;
                }
            }
        }
    }

}
